﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.IO;

namespace HSFB.Lib
{
    /// <summary>
    /// Fizz Buzz excercise
    /// </summary>
    public static class FB
    {
        /// <summary>
        /// Applies number divisibilty rules 
        /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
        private static string GetValue(int i,
            string label_3, string label_5)
        {

            var value = i.ToString();

            var div3 = (i % 3 == 0);
            var div5 = (i % 5 == 0);

            if (div3 && div5)
                value = label_3 + label_5;
            else if (div3)
                value = label_3;
            else if (div5)
                value = label_5;

            return value;
        }

        /// <summary>
        /// Runs FizzBuzz and returns string values
        /// </summary>
        /// <param name="start">Min value</param>
        /// <param name="end">Max value</param>
        /// <param name="label_3">Label for divisible by 3</param>
        /// <param name="label_5">Label for divisible by 5</param>
        /// <returns>List of string values</returns>
        private static List<string> _Run(int start, int end,
            string label_3, string label_5)
        {
            var values = new List<string>();

            for (var i = start; i <= end; i++)
                values.Add(
                    GetValue(i, label_3, label_5));

            return values;
        }

        /// <summary>
        /// Outputs values to System.Console
        /// </summary>
        /// <param name="values"></param>
        private static void DefaultOutput(List<string> values)
        {
            values.ForEach(
                value => Console.WriteLine(value));
        }

        /// <summary>
        /// Runs FizzBuzz 
        /// Outputs to Console
        /// </summary>
        /// <param name="start">Min value</param>
        /// <param name="end">Max value</param>
        /// <param name="label_3">Label for divisible by 3. Default value: Fizz</param>
        /// <param name="label_5">Label for divisible by 5. Default value: Buzz</param>
        public static void Run(
            int start, int end,
            string label_3 = "Fizz", string label_5 = "Buzz")
        {
            //Run FizzBuzz
            //Default to console
            DefaultOutput(_Run(start, end, label_3, label_5));
        }

        /// <summary>
        /// Runs FizzBuzz 
        /// Outputs to specified Trace Listener if provided, otherwise defaults to Console
        /// </summary>
        /// <param name="output">Trace listener to output values</param>      
        /// <param name="start">Min value</param>
        /// <param name="end">Max value</param>
        /// <param name="label_3">Label for divisible by 3. Default value: Fizz</param>
        /// <param name="label_5">Label for divisible by 5. Default value: Buzz</param>
        public static void Run(
            TraceListener output,
            int start, int end,
            string label_3 = "Fizz", string label_5 = "Buzz")
        {
            //Run FizzBuzz
            var values = _Run(start, end, label_3, label_5);

            if (output != null)
                //Output to trace listener
                values.ForEach(
                    value => output.WriteLine(value));
            else
                //Default to console
                DefaultOutput(values);
        }

        /// <summary>
        /// Runs FizzBuzz
        /// Outputs to collection<string> if provided, otherwise defaults to Console
        /// </summary>
        /// <param name="output">Collection<string> to output values</param> 
        /// <param name="start">Min value</param>
        /// <param name="end">Max value</param>
        /// <param name="label_3">Label for divisible by 3. Default value: Fizz</param>
        /// <param name="label_5">Label for divisible by 5. Default value: Buzz</param>
        public static void Run(
            ICollection<string> output,
            int start, int end,
            string label_3 = "Fizz", string label_5 = "Buzz")
        {
            //Run FizzBuzz
            var values = _Run(start, end, label_3, label_5);

            if (output != null)
                //appends to collection
                values.ForEach(
                    value => output.Add(value));
            else
                //Default to console
                DefaultOutput(values);
        }

        /// <summary>
        /// Runs FizzBuzz
        /// Outputs to TextWriter if provided, otherwise defaults to Console
        /// </summary>
        /// <param name="output">TextWriter to output values</param>
        /// <param name="start">Min value</param>
        /// <param name="end">Max value</param>
        /// <param name="label_3">Label for divisible by 3. Default value: Fizz</param>
        /// <param name="label_5">Label for divisible by 5. Default value: Buzz</param>
        public static void Run(
            TextWriter output,
            int start, int end,
            string label_3 = "Fizz", string label_5 = "Buzz")
        {
            //Run FizzBuzz
            var values = _Run(start, end, label_3, label_5);

            if (output != null)
                //outputs to textwriter
                values.ForEach(
                    value => output.WriteLine(value));
            else
                //Default to console
                DefaultOutput(values);
        }

        /// <summary>
        /// Runs FizzBuzz
        /// Outputs to Stream if provided, otherwise defaults to Console
        /// </summary>
        /// <param name="start">Min value</param>
        /// <param name="end">Max value</param>
        /// <param name="label_3">Label for divisible by 3. Default value: Fizz</param>
        /// <param name="label_5">Label for divisible by 5. Default value: Buzz</param>
        /// <param name="output">Stream to output values</param>
        public static void Run(
            Stream output,
            Encoding encoding,
            int start, int end,
            string label_3 = "Fizz", string label_5 = "Buzz")
        {
            //Run FizzBuzz
            var values = _Run(start, end, label_3, label_5);

            if (output != null && output.CanWrite)
                //outputs to stream
                values.ForEach(
                    value =>
                    {
                        var bytes = encoding.GetBytes(value + Environment.NewLine);
                        output.Write(bytes, 0, bytes.Length);
                    });
            else
                //Default to console
                DefaultOutput(values);
        }

        /// <summary>
        /// Runs FizzBuzz
        /// Outputs to IFBWriter if provided, otherwise defaults to Console
        /// </summary>
        /// <param name="output">IFBWriter to output values</param>
        /// <param name="start">Min value</param>
        /// <param name="end">Max value</param>
        /// <param name="label_3">Label for divisible by 3. Default value: Fizz</param>
        /// <param name="label_5">Label for divisible by 5. Default value: Buzz</param>
        public static void Run(
            IFBWriter output,
            int start, int end,
            string label_3 = "Fizz", string label_5 = "Buzz")
        {
            //Run FizzBuzz
            var values = _Run(start, end, label_3, label_5);

            if (output != null)
                //outputs to IFBWriter
                values.ForEach(
                    value => output.WriteLine(value));
            else
                //Default to console
                DefaultOutput(values);
        }
    }
}
